﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Media;
using System.Windows;
using System.Reflection;


namespace Mesuro.Graphing.Utilities {
	public delegate void PlotRenderDelegate( Plot plot, DrawingContext cx );
	public static class GraphingRegistry {
		private struct RendererInfo {
			public PlotRenderDelegate Renderer { get; set; }

			public Type PlotContent { get; set; }
			public Type[] ContentConstructorParameterTypes { get; set; }
			public object[] ContentConstructorParameters { get; set; }

			public Type AdditionalProperties { get; set; }
		}

		private struct AxesInfo {
			public Type Axes { get; set; }
			public Type[] ConstructorParameterTypes { get; set; }
			public object[] ConstructorParameters { get; set; }

			public Type AdditionalProperties { get; set; }
		}

		#region Renderers
		private static Dictionary<String, RendererInfo> _rendererMap = new Dictionary<String, RendererInfo>();
		public static string DefaultRendererType { get; set; }

		public static event EventHandler RendererListChanged;

		public static bool RegisterRenderer( String name, Type plotContent, Type[] plotContentConstructorParameterTypes = null, Object[] plotContentConstructorParameters = null, Type additionalProperties = null ) {
			return RegisterRenderer( name, null, plotContent, plotContentConstructorParameterTypes, plotContentConstructorParameters, additionalProperties );
		}
		public static bool RegisterRenderer( String name, PlotRenderDelegate renderer, Type plotContent = null, Type[] plotContentConstructorParameterTypes = null, Object[] plotContentConstructorParameters = null, Type additionalProperties = null ) {
			if (_rendererMap.ContainsKey( name ))
				return false;

			if (plotContent != null && !plotContent.IsSubclassOf( typeof( Visual ) ))
				throw new ArgumentException( "Must derive from System.Windows.Media.Visual.", "plotContent" );

			if (renderer == null && plotContent == null)
				throw new ArgumentException( "At least one of plotContent and renderer must be non-null." );

			if (plotContentConstructorParameters == null && plotContentConstructorParameterTypes != null)
				throw new ArgumentNullException( "Types provided without values. Use new object{null} to provide a null value to the constructor", "plotContentConstructorParameters" );

			if (plotContentConstructorParameters != null && plotContentConstructorParameterTypes == null)
				throw new ArgumentNullException( "Values provided without types.", "plotContentConstructorParameterTypes" );

			if (plotContentConstructorParameterTypes == null)
				plotContentConstructorParameterTypes = Type.EmptyTypes;

			else if (plotContentConstructorParameters.Length != plotContentConstructorParameterTypes.Length)
				throw new ArgumentException( "Unequal length parameter and parameter type arrays." );

			_rendererMap.Add( name, new RendererInfo() {
				Renderer = renderer,
				PlotContent = plotContent,
				ContentConstructorParameterTypes = plotContentConstructorParameterTypes,
				ContentConstructorParameters = plotContentConstructorParameters,
				AdditionalProperties = additionalProperties
			} );

			if (DefaultRendererType == null)
				DefaultRendererType = name;

			if (RendererListChanged != null)
				RendererListChanged( null, new EventArgs() );

			return true;
		}

		public static bool DeregisterRenderer( String name ) {
			bool ret = _rendererMap.Remove( name );

			if (RendererListChanged != null)
				RendererListChanged( null, new EventArgs() );

			return ret;
		}

		public static String[] GetRendererNames() {
			string[] ret = new string[_rendererMap.Count];
			_rendererMap.Keys.CopyTo( ret, 0 );
			return ret;
		}

		public static PlotRenderDelegate GetRenderer( String name = null ) {
			if (name == null) return _rendererMap[DefaultRendererType].Renderer;
			return _rendererMap[name].Renderer;
		}

		public static PlotRenderDelegate GetDefaultRenderer() {
			return (DefaultRendererType == null) ? null : GetRenderer( DefaultRendererType );
		}

		public static Visual CreatePlotContent( Plot plot, string renderType ) {
			RendererInfo info;
			//Get the info object
			if (!_rendererMap.TryGetValue( renderType, out info ))
				throw new ArgumentException( "No matching registration found.", "renderType" );

			//If there isn't any content, return null
			if (info.PlotContent == null) return null;

			//Create a working copy of the parameters array
			object[] ctorParams;
			if (info.ContentConstructorParameters != null) {
				ctorParams = new object[info.ContentConstructorParameters.Length];
				info.ContentConstructorParameters.CopyTo( ctorParams, 0 );
			} else
				ctorParams = new object[0];

			//If necessary, insert the basedOn into the parameter array (allows the basedOn content to accces the parent)
			int idx = Array.IndexOf( info.ContentConstructorParameterTypes, typeof( Plot ) );
			if (idx >= 0 && idx < ctorParams.Length && ctorParams[idx] == null)
				ctorParams[idx] = plot;

			ConstructorInfo ctor = info.PlotContent.GetConstructor( info.ContentConstructorParameterTypes );

			if (ctor == null) throw new InvalidOperationException( "Null constructor" );

			return (Visual)ctor.Invoke( ctorParams );
		}

		public static Type GetAdditionalProperties( Plot element ) {
			RendererInfo info;
			if (_rendererMap.TryGetValue( element.RenderType, out info ))
				return info.AdditionalProperties;
			return null;
		}
		#endregion

		#region Axes
		private static Dictionary<String, AxesInfo> _axisMap = new Dictionary<String, AxesInfo>();
		public static string DefaultAxesType { get; set; }

		public static event EventHandler AxesTypeListChanged;

		public static bool RegisterAxes( String name, Type tAxes, Type tAdditionalProperties ) {
			return RegisterAxes( name, tAxes, new Type[0], new object[0], tAdditionalProperties );
		}
		public static bool RegisterAxes( String name, Type tAxes, Type[] ctorParameterTypes, Object[] ctorParameters, Type tAdditionalProperties ) {
			if (ctorParameterTypes == null)
				ctorParameterTypes = new Type[0];
			if (ctorParameters == null)
				ctorParameters = new Object[0];
			if (ctorParameterTypes.Length != ctorParameters.Length)
				throw new ArgumentException( "Unequal length arrays for ctorParameterTypes and ctorParameters" );
			if (tAxes == null)
				throw new ArgumentNullException( "Cannot register null axes", "tAxes" );
			if (name == null)
				throw new ArgumentNullException( "Cannot register axes with null name", "name" );

			if (_axisMap.ContainsKey( name ))
				return false;

			AxesInfo info = new AxesInfo {
				Axes = tAxes,
				ConstructorParameterTypes = ctorParameterTypes,
				ConstructorParameters = ctorParameters,
				AdditionalProperties = tAdditionalProperties
			};

			_axisMap.Add( name, info );

			if (DefaultAxesType == null)
				DefaultAxesType = name;

			if (AxesTypeListChanged != null)
				AxesTypeListChanged( null, new EventArgs() );

			return true;
		}

		public static bool DeregisterAxes( String name ) {
			bool ret = _axisMap.Remove( name );

			if (AxesTypeListChanged != null)
				AxesTypeListChanged( null, new EventArgs() );

			return ret;
		}

		public static String[] GetAxesNames() {
			string[] ret = new string[_axisMap.Count];
			_axisMap.Keys.CopyTo( ret, 0 );
			return ret;
		}

		public static FrameworkElementFactory CreateAxesFactory( String name = null ) {
			if (name == null)
				name = DefaultAxesType;

			AxesInfo info;
			if (!_axisMap.TryGetValue( name, out info ))
				throw new ArgumentException( "Cannot find axes with name: " + name );

			return new FrameworkElementFactory( info.Axes );
		}
		public static FrameworkElementFactory CreateDefaultAxes( String name = null ) {
			return CreateAxesFactory( DefaultAxesType );
		}

		public static Type GetAdditionalProperties( Graph graph ) {
			AxesInfo info;
			if (_axisMap.TryGetValue( graph.AxesType, out info ))
				return info.AdditionalProperties;
			return null;
		}

		//public static AxesProperties CreateAdditionalProperties( String axesType, Axes axes ) {
		//    if (axesType == null)
		//        axesType = DefaultAxesType;

		//    AxesInfo info;
		//    if (!_axisMap.TryGetValue( axesType, out info ))
		//        throw new ArgumentException( "Cannot find axes with seriesKey: " + axesType );

		//    if (info.AdditionalProperties == null)
		//        return null;

		//    ConstructorInfo cAxes = info.AdditionalProperties.GetConstructor( Type.EmptyTypes );

		//    if (cAxes == null)
		//        throw new Exception( "Null constructor" );

		//    AxesProperties props = (AxesProperties)cAxes.Invoke( info.ConstructorParameters );

		//    if (props == null)
		//        throw new Exception( "Null constructed" );

		//    props.Axes = axes;

		//    return props;
		//}
		public static String GetType( Axes axes ) {
			foreach (KeyValuePair<String, AxesInfo> pair in _axisMap) {
				if (pair.Value.Axes.Equals( axes ))
					return pair.Key;
			}
			return "Unknown Axes";
		}
		#endregion
	}
}
